﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using MC.Phone.Ads.Provider;
using MC.Phone.Ads.Rx;
using Microsoft.Phone.Reactive;

namespace MC.Phone.Ads.Strategy
{
    public class DefaultAdProviderStrategy : DependencyObject, IAdProviderStrategy, INotifyPropertyChanged
    {
        public const string PausedPropertyName = "Paused";

        public static readonly DependencyProperty FallbackAdProviderNameProperty =
            DependencyProperty.Register("FallbackAdProviderName", typeof (string), typeof (DefaultAdProviderStrategy),
                                        null);

        private bool _paused;

        private IList<AdProviderToUse> AdProvidersToUse { get; set; }

        #region IAdProviderStrategy Members

        public IAdProvider CurrentAdProvider { get; private set; }

        public IEnumerable<IAdProvider> AdProviders { get; set; }

        public IObservable<IAdProvider> ProvideNext()
        {
            Debug.Assert(AdProviders.Any(), "Please add an AdProvider to the AdProviders collection.");
            Initialize();

            IObservable<IAdProvider> adProviderObservable = new AnonymousObservable<IAdProvider>(
                observer =>
                    {
                        if (!DesignerProperties.IsInDesignTool)
                            SelectBestNextAdProvider(observer);

                        return Disposable.Empty;
                    });
            return adProviderObservable;
        }

        public string CountryCode { set; private get; }

        public bool Paused
        {
            get { return _paused; }
            set
            {
                if (_paused != value)
                {
                    _paused = value;
                    RaisePropertyChanged(PausedPropertyName);
                }
            }
        }

        public WhenOffline WhenOffline { get; set; }

        public string FallbackAdProviderName
        {
            get { return (string) GetValue(FallbackAdProviderNameProperty); }
            set { SetValue(FallbackAdProviderNameProperty, value); }
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private void SelectBestNextAdProvider(IObserver<IAdProvider> observer)
        {
            if (!Paused)
            {
                IAdProvider provider = AdProviders.FirstOrDefault(
                    ap =>
                    ap.Name ==
                    AdProvidersToUse.OrderBy(apt => apt.AmountOfNoAd).ThenBy(apt => apt.Order).Select(apt => apt.Name).
                        FirstOrDefault());
                if (provider != null)
                {
                    AdProviderToUse providerToUse = AdProvidersToUse.First(apt => apt.Name == provider.Name);
                    //Make sure that you're not requesting the same adprovider within 1 minute.
                    TimeSpan timeSpanSinceLastTimeNoAdOnProvider = DateTime.Now.Subtract(providerToUse.LastTimeNoAd);
                    if (timeSpanSinceLastTimeNoAdOnProvider < TimeSpan.FromMinutes(1))
                    {
                        Debug.WriteLine( Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - Start sleeping");
                        TimeSpan sleepTime = TimeSpan.FromMinutes(1).Subtract(timeSpanSinceLastTimeNoAdOnProvider);
                        Thread.Sleep(sleepTime);
                        Debug.WriteLine( Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - Finished sleeping");
                    }

                    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - NoAd provider");

                    Observable.FromEvent<AdEventArgs>(provider, "NoAd")
                        .Take(1)
                        .ObserveOn(Scheduler.ThreadPool)
                        .Subscribe(
                            e =>
                                {
                                    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - RJ1");
                                    AdProviderToUse providerWithNoAd =
                                        AdProvidersToUse.First(apt => apt.Name == e.EventArgs.AdProviderName);
                                    providerWithNoAd.AmountOfNoAd++;
                                    providerWithNoAd.LastTimeNoAd = DateTime.Now;
                                    SelectBestNextAdProvider(observer);
                                });
                    CurrentAdProvider = provider;
                    Debug.WriteLine( Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - {1:HH:mm:ss}:{0}", provider.Name, DateTime.Now);
                    observer.OnNext(provider);
                }
            }
            else
            {
                Debug.WriteLine( Thread.CurrentThread.ManagedThreadId + "WPUnifiedAd - AdProviderStrategy paused.");
                Observable.FromEvent<PropertyChangedEventArgs>(this, "PropertyChanged")
                    .Where(e => e.EventArgs.PropertyName == "Paused")
                    .Where(e => !Paused)
                    .Take(1)
                    .Subscribe(
                        e =>
                            {
                                Debug.WriteLine( Thread.CurrentThread.ManagedThreadId + 
                                    "WPUnifiedAd - AdProviderStrategy unpaused.");
                                SelectBestNextAdProvider(observer);
                            });
            }
        }

        private void Initialize()
        {
            if (AdProvidersToUse == null)
            {
                AdProvidersToUse = new List<AdProviderToUse>(AdProviders.Count());
                int order = 0;
                {
                    IAdProvider adProvider = AdProviders.FirstOrDefault(ap => ap.Name == FallbackAdProviderName);
                    if (adProvider != null)
                    {
                        AdProvidersToUse.Add(new AdProviderToUse {Order = order, Name = adProvider.Name});
                        order++;
                    }
                }
                foreach (IAdProvider adProvider in AdProviders.Where(ap => ap.Name != FallbackAdProviderName))
                {
                    AdProvidersToUse.Add(new AdProviderToUse {Order = order, Name = adProvider.Name});
                    order++;
                }
            }
        }

        #region Nested type: AdProviderToUse

        private class AdProviderToUse
        {
            public string Name { get; set; }
            public int Order { get; set; }
            public int AmountOfNoAd { get; set; }
            public DateTime LastTimeNoAd { get; set; }
        }

        #endregion
    }
}